Skip to content

Feat: 리크루팅 지원 결과 api 구현#138

Merged
mjy926 merged 2 commits into
developfrom
feat/submission-result
May 17, 2026
Merged

Feat: 리크루팅 지원 결과 api 구현#138
mjy926 merged 2 commits into
developfrom
feat/submission-result

Conversation

@mjy926

@mjy926 mjy926 commented May 15, 2026

Copy link
Copy Markdown
Contributor
  • 특정 recruiting id에 해당하는 지원 결과를 반환하는 api를 구현했습니다.

@mjy926 mjy926 requested a review from a team as a code owner May 15, 2026 08:08
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5c8fb5a8-3511-45f7-b8db-a5e2e4f33438

📥 Commits

Reviewing files that changed from the base of the PR and between a0bbbc1 and 6e3155a.

📒 Files selected for processing (1)
  • wacruit/src/apps/recruiting/repositories.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • wacruit/src/apps/recruiting/repositories.py

📝 Walkthrough

Walkthrough

모집별 지원자 제출 현황을 한 화면에 표시하기 위해 이력서 답변과 코드 제출 정답 카운트를 집계하는 조회 기능을 추가했습니다. 응답 스키마 정의부터 저장소의 SQL 피벗 쿼리, 서비스 계층 조합, FastAPI 라우트 노출까지 전체 계층을 구현합니다.

Changes

모집 지원자 제출 현황 조회

Layer / File(s) Summary
응답 스키마 계약
wacruit/src/apps/recruiting/schemas.py
typing 임포트 정리 및 RecruitingSubmissionResponse(제출 항목), RecruitingSubmissionListResponse(items 배열) 스키마 추가로 응답 데이터 계약 수립.
저장소: import 정리 및 모델 의존성
wacruit/src/apps/recruiting/repositories.py
Alembic 불필요 import 제거, sqlalchemy.case, CodeSubmissionResult/CodeSubmissionResultStatus 등 쿼리 구현에 필요한 import 및 모델 의존성 추가.
저장소 쿼리 계층
wacruit/src/apps/recruiting/repositories.py
get_submissions_by_recruiting_id 메서드 추가: 모집의 질문 3개·문제 3개를 조회 후 최근 제출을 서브쿼리로 피벗하여 이력서 답변(q1q3), 최신 코드 제출(source_code) 및 코드 정답 카운트(problem_1_correctproblem_3_correct)를 집계해 반환. limit/offset 페이징 포함.
서비스 계층
wacruit/src/apps/recruiting/services.py
get_recruiting_submissions 메서드 추가: 저장소 조회 결과를 RecruitingSubmissionResponse 객체로 변환하고 RecruitingSubmissionListResponse로 감싸 반환.
API 엔드포인트
wacruit/src/apps/recruiting/views_v3.py
GET /v3/recruitings/{recruiting_id}/submission 라우트 추가: admin 권한 파라미터와 limit/offset 쿼리로 서비스를 호출하여 RecruitingSubmissionListResponse를 반환.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 특정 모집 ID의 지원 결과를 반환하는 API 구현이라는 주요 변경 사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 특정 recruiting ID에 해당하는 지원 결과를 반환하는 API 구현이라는 변경 사항과 관련이 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/submission-result

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
wacruit/src/apps/recruiting/repositories.py (1)

357-365: 💤 Low value

중복된 coalesce 처리가 있습니다.

라인 302-341의 code_pivot 서브쿼리에서 이미 func.coalesce(..., 0)로 NULL을 0으로 처리했는데, 라인 357-365의 최종 select에서 다시 func.coalesce(code_pivot.c.problem_X_correct, 0)를 적용하고 있습니다. 이는 불필요한 중복입니다.

♻️ 제안하는 중복 제거
             resume_pivot.c.q1_answer,
             resume_pivot.c.q2_answer,
             resume_pivot.c.q3_answer,
-            func.coalesce(code_pivot.c.problem_1_correct, 0).label(
-                "problem_1_correct"
-            ),
-            func.coalesce(code_pivot.c.problem_2_correct, 0).label(
-                "problem_2_correct"
-            ),
-            func.coalesce(code_pivot.c.problem_3_correct, 0).label(
-                "problem_3_correct"
-            ),
+            code_pivot.c.problem_1_correct,
+            code_pivot.c.problem_2_correct,
+            code_pivot.c.problem_3_correct,
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacruit/src/apps/recruiting/repositories.py` around lines 357 - 365, The
final SELECT redundantly wraps code_pivot's already-coalesced columns with
func.coalesce again; remove the extra coalesce calls in the projection and
reference code_pivot.c.problem_1_correct, code_pivot.c.problem_2_correct,
code_pivot.c.problem_3_correct (etc.) directly while preserving their
.label("problem_X_correct") names so the previously-applied coalesce in the
code_pivot subquery remains the single source of NULL->0 handling.
wacruit/src/apps/recruiting/schemas.py (1)

16-17: 💤 Low value

빈 TYPE_CHECKING 블록은 제거를 고려하세요.

TYPE_CHECKING 블록 내부에 실제 임포트가 없다면 블록 전체를 제거하는 것이 더 깔끔합니다.

♻️ 제안하는 정리 방안
-if TYPE_CHECKING:
-    pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacruit/src/apps/recruiting/schemas.py` around lines 16 - 17, Remove the
empty TYPE_CHECKING block in wacruit/src/apps/recruiting/schemas.py: locate the
"if TYPE_CHECKING:" block and delete it (including its pass) since there are no
conditional type-only imports; if you later need type-only imports retain
TYPE_CHECKING and add the imports inside the block.
wacruit/src/apps/recruiting/views_v3.py (1)

88-96: ⚡ Quick win

쿼리 파라미터에 검증 제약 조건을 추가하는 것을 고려하세요.

FastAPI의 Query를 사용하면서 ge (greater than or equal), le (less than or equal) 등의 제약 조건을 설정하지 않았습니다. 서비스 계층에서 검증할 수도 있지만, API 계층에서 명시하면 자동 문서화와 조기 검증이 가능합니다.

📝 제안하는 제약 조건 추가
 `@v3_router.get`("/{recruiting_id}/submission")
 def get_recruiting_submission(
     admin_user: AdminUser,
     recruiting_id: int,
     recruiting_service: Annotated[RecruitingService, Depends()],
-    limit: int = Query(100),
-    offset: int = Query(0),
+    limit: int = Query(100, ge=1, le=1000),
+    offset: int = Query(0, ge=0),
 ) -> RecruitingSubmissionListResponse:
     return recruiting_service.get_recruiting_submissions(recruiting_id, limit, offset)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacruit/src/apps/recruiting/views_v3.py` around lines 88 - 96, The
get_recruiting_submission endpoint currently declares limit and offset via Query
without validation; update the parameter declarations in
get_recruiting_submission to add appropriate Query constraints (e.g., limit with
ge=1 and a sensible le like 1000, offset with ge=0) so FastAPI performs early
validation and documents these bounds; modify the Query(...) calls for the limit
and offset parameters used in the get_recruiting_submission function signature
to include these ge/le constraints.
wacruit/src/apps/recruiting/services.py (1)

283-292: ⚡ Quick win

limit/offset 파라미터 검증이 누락되었습니다.

음수 값이나 비정상적으로 큰 값이 전달될 수 있습니다. 페이징 파라미터에 대한 경계 검증을 추가하는 것이 좋습니다.

🛡️ 제안하는 검증 추가
     def get_recruiting_submissions(
         self, recruiting_id: int, limit: int, offset: int
     ) -> RecruitingSubmissionListResponse:
+        if limit <= 0 or limit > 1000:
+            raise ValueError("Limit must be between 1 and 1000")
+        if offset < 0:
+            raise ValueError("Offset must be non-negative")
+        
         recruiting = self.recruiting_repository.get_recruiting_by_id(recruiting_id)
         if recruiting is None:
             raise RecruitingNotFoundException()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacruit/src/apps/recruiting/services.py` around lines 283 - 292, The
get_recruiting_submissions method is missing validation for limit/offset
allowing negative or huge values; add checks at the start of
get_recruiting_submissions to ensure offset >= 0, limit > 0 and limit <=
RECRUITING_MAX_LIMIT (introduce a RECRUITING_MAX_LIMIT constant), coerce or
reject non-int inputs, and raise a clear exception (e.g., ValueError or a
domain-specific InvalidArgument) when validation fails before calling
recruiting_repository.get_submissions_by_recruiting_id; include the parameter
names (limit, offset) in the error message for easier debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacruit/src/apps/recruiting/repositories.py`:
- Around line 276-285: The subquery latest_code currently selects only
CodeSubmission.id, user_id, problem_id but not CodeSubmission.code, which causes
RecruitingSubmissionResponse (with problem_1_code/problem_2_code/problem_3_code)
to miss data; fix by adding CodeSubmission.code to the latest_code select and
ensure the final query (the projection that builds RecruitingSubmissionResponse)
selects/aliases the code values into the expected fields (e.g., map codes for
each problem to problem_1_code/problem_2_code/problem_3_code) so the row dict
passed to RecruitingSubmissionResponse(**row) contains those keys;
alternatively, if code is truly unnecessary, remove the problem_*_code fields
from the RecruitingSubmissionResponse schema instead.
- Around line 194-220: The code accesses question_ids[0..2] and
problem_ids[0..2] without validating lengths, risking IndexError; update the
block after fetching question_ids and problem_ids (the results of
self.session.execute(select(ResumeQuestion.id)...) and select(Problem.id)...) to
first check that len(question_ids) >= 3 and len(problem_ids) >= 3 and if not
raise a clear exception (e.g., ValueError including recruiting_id) or handle the
short-case appropriately, then safely assign q1_id,q2_id,q3_id and
p1_id,p2_id,p3_id (or use tuple unpacking after the length check) so the code
never indexes out of range.
- Around line 191-193: Add an explicit return type annotation to the method
get_submissions_by_recruiting_id to improve type checking and readability;
update its signature to include an appropriate return type such as
list[Submission] or Sequence[Submission] (or the project's ORM type e.g.,
QuerySet[Submission]) and import the needed typing symbol (List/Sequence) and
Submission model/type if not already imported, making sure the annotated type
matches the actual value returned by the method.

In `@wacruit/src/apps/recruiting/services.py`:
- Around line 283-292: get_recruiting_submissions currently returns an empty
list for unknown recruiting_id; add an existence check at the start by reusing
the existing validation logic (call self.get_recruiting_by_id(recruiting_id) or
use the repository existence/get method used by
get_recruiting_by_id/get_user_recruiting_by_id) and let it raise the same
NotFound/validation error when the recruiting doesn't exist, then proceed to
fetch submissions and build RecruitingSubmissionListResponse only if the
recruiting exists.

---

Nitpick comments:
In `@wacruit/src/apps/recruiting/repositories.py`:
- Around line 357-365: The final SELECT redundantly wraps code_pivot's
already-coalesced columns with func.coalesce again; remove the extra coalesce
calls in the projection and reference code_pivot.c.problem_1_correct,
code_pivot.c.problem_2_correct, code_pivot.c.problem_3_correct (etc.) directly
while preserving their .label("problem_X_correct") names so the
previously-applied coalesce in the code_pivot subquery remains the single source
of NULL->0 handling.

In `@wacruit/src/apps/recruiting/schemas.py`:
- Around line 16-17: Remove the empty TYPE_CHECKING block in
wacruit/src/apps/recruiting/schemas.py: locate the "if TYPE_CHECKING:" block and
delete it (including its pass) since there are no conditional type-only imports;
if you later need type-only imports retain TYPE_CHECKING and add the imports
inside the block.

In `@wacruit/src/apps/recruiting/services.py`:
- Around line 283-292: The get_recruiting_submissions method is missing
validation for limit/offset allowing negative or huge values; add checks at the
start of get_recruiting_submissions to ensure offset >= 0, limit > 0 and limit
<= RECRUITING_MAX_LIMIT (introduce a RECRUITING_MAX_LIMIT constant), coerce or
reject non-int inputs, and raise a clear exception (e.g., ValueError or a
domain-specific InvalidArgument) when validation fails before calling
recruiting_repository.get_submissions_by_recruiting_id; include the parameter
names (limit, offset) in the error message for easier debugging.

In `@wacruit/src/apps/recruiting/views_v3.py`:
- Around line 88-96: The get_recruiting_submission endpoint currently declares
limit and offset via Query without validation; update the parameter declarations
in get_recruiting_submission to add appropriate Query constraints (e.g., limit
with ge=1 and a sensible le like 1000, offset with ge=0) so FastAPI performs
early validation and documents these bounds; modify the Query(...) calls for the
limit and offset parameters used in the get_recruiting_submission function
signature to include these ge/le constraints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c97e7385-4628-474d-9994-a65f573bde14

📥 Commits

Reviewing files that changed from the base of the PR and between c08ad50 and a0bbbc1.

📒 Files selected for processing (4)
  • wacruit/src/apps/recruiting/repositories.py
  • wacruit/src/apps/recruiting/schemas.py
  • wacruit/src/apps/recruiting/services.py
  • wacruit/src/apps/recruiting/views_v3.py

Comment on lines +191 to +193
def get_submissions_by_recruiting_id(
self, recruiting_id: int, limit: int, offset: int
):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

반환 타입 주석이 누락되었습니다.

메서드의 반환 타입을 명시하면 타입 체킹과 코드 가독성이 향상됩니다.

📝 제안하는 타입 주석 추가
-    def get_submissions_by_recruiting_id(
-        self, recruiting_id: int, limit: int, offset: int
-    ):
+    def get_submissions_by_recruiting_id(
+        self, recruiting_id: int, limit: int, offset: int
+    ) -> Sequence[dict]:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacruit/src/apps/recruiting/repositories.py` around lines 191 - 193, Add an
explicit return type annotation to the method get_submissions_by_recruiting_id
to improve type checking and readability; update its signature to include an
appropriate return type such as list[Submission] or Sequence[Submission] (or the
project's ORM type e.g., QuerySet[Submission]) and import the needed typing
symbol (List/Sequence) and Submission model/type if not already imported, making
sure the annotated type matches the actual value returned by the method.

Comment thread wacruit/src/apps/recruiting/repositories.py Outdated
Comment thread wacruit/src/apps/recruiting/repositories.py
Comment on lines +283 to +292
def get_recruiting_submissions(
self, recruiting_id: int, limit: int, offset: int
) -> RecruitingSubmissionListResponse:
raw_submissions = self.recruiting_repository.get_submissions_by_recruiting_id(
recruiting_id, limit, offset
)

parsed_items = [RecruitingSubmissionResponse(**row) for row in raw_submissions]

return RecruitingSubmissionListResponse(items=parsed_items)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

recruiting_id 존재 여부 검증이 누락되었습니다.

다른 메서드들(get_recruiting_by_id, get_user_recruiting_by_id 등)과 달리 이 메서드는 recruiting_id의 유효성을 검증하지 않습니다. 존재하지 않는 recruiting_id로 호출해도 빈 리스트를 반환하여, 클라이언트가 "존재하지 않는 모집"과 "지원자가 없는 모집"을 구분할 수 없습니다.

🛡️ 제안하는 검증 추가
     def get_recruiting_submissions(
         self, recruiting_id: int, limit: int, offset: int
     ) -> RecruitingSubmissionListResponse:
+        recruiting = self.recruiting_repository.get_recruiting_by_id(recruiting_id)
+        if recruiting is None:
+            raise RecruitingNotFoundException()
+        
         raw_submissions = self.recruiting_repository.get_submissions_by_recruiting_id(
             recruiting_id, limit, offset
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacruit/src/apps/recruiting/services.py` around lines 283 - 292,
get_recruiting_submissions currently returns an empty list for unknown
recruiting_id; add an existence check at the start by reusing the existing
validation logic (call self.get_recruiting_by_id(recruiting_id) or use the
repository existence/get method used by
get_recruiting_by_id/get_user_recruiting_by_id) and let it raise the same
NotFound/validation error when the recruiting doesn't exist, then proceed to
fetch submissions and build RecruitingSubmissionListResponse only if the
recruiting exists.

@mjy926 mjy926 merged commit a6c3b1e into develop May 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants